#include "mylexer.hpp"
#include "CodeParser.hpp"
#include "Value.hpp"
#include "ParsingErrorException.hpp"

Lexer::Lexer(InputStream * stream)
: Object()
, stream_(stream)
, codeBlockType_(OutsideCodeBlock)
, commentType_(OutsideComment)
, state_(UnitializedText)
, hasCode_(false)
, semiconPending_(false)
{
	codeParser_ = new CodeParser(stream_);
}

Lexer::~Lexer()
{

}

int Lexer::yylex()
{	
	value_ = ObjectPtr();
	location_ = SourceLocation();

	if(semiconPending_)
	{
		semiconPending_ = false;
		location_ = semicolonLocation_;
		return ';';
	}

	String textBuffer;

	for(;;)
	{
		bool flushText = false;
		bool parseCode = false;

		{
			InputGetter g(stream_);
			char c = g.ch();

			switch(state_)
			{
				case UnitializedText:
				{
					stream_->startParsing();
					textBuffer.clear();
					state_ = Text;
					/* continue to Text */
				}
				case Text:
				{
					if(g.eof())
					{
						location_ = stream_->consumedLocation();
						stream_->startParsing();
						flushText = true;
						state_ = EndOfFile;
					}
					else if(c == '@')
					{
						location_ = stream_->consumedLocation();
						stream_->startParsing();
						state_ = AtSign;
					}
					else
					{
						textBuffer.push_back(c);
					}
					break;
				}
				case AtSign:
				{
					flushText = true;

					if(c == '@')
					{
						state_ = Code;
						codeBlockType_ = EndOfLineBlock;
					}
					else if(c == '{')
					{
						state_ = Code;
						codeBlockType_ = MultiLineBlock;
						break;
					}
					else if(c == '/')
					{
						state_ = Comment;
						commentType_ = EndOfLineComment;
					}
					else if(c == '*')
					{
						state_ = Comment;
						commentType_ = MultiLineComment;
					}
					else
					{
						g.discard();
						state_ = Code;
						codeBlockType_ = InLineBlock;
					}
					break;
				}
				case Comment:
				{
					assert(commentType_ != OutsideComment);

					if(commentType_ == EndOfLineComment && c == '\n')
					{
						state_ = UnitializedText;
					}
					else if(commentType_ == MultiLineComment && c == '*')
					{
						state_ = MaybeEndOfComment;
					}
					break;
				}
				case MaybeEndOfComment:
				{
					if(c == '@')
					{
						commentType_ = OutsideComment;
						state_ = UnitializedText;
					}
					else
					{
						state_ = Comment;
					}
					break;
				}
				case Code:
				{
					if(codeBlockType_ == InLineBlock)
					{
						if(c == '@')
						{
							state_ = UnitializedText;
							stream_->startParsing();
							location_ = stream_->location();
							if(hasCode_) return ';';
						}
						else if(c == '\n')
						{
							throw ParsingErrorException(stream_->cursorLocation(), "Unterminated in-line code block");
						}
						else
						{
							g.discard();
						}
					}
					else if(codeBlockType_ == EndOfLineBlock)
					{
						if(c == '\n')
						{
							state_ = UnitializedText;
							location_ = stream_->cursorLocation();
							if(hasCode_) return ';';
						}
						else
						{
							g.discard();
						}
					}
					else if(codeBlockType_ == MultiLineBlock)
					{
						if(c == '}')
						{
							stream_->startParsing();
							state_ = MaybeEndOfBlock;
						}
						else if(c == '\n')
						{
							/* do nothing, eat this EOL */
						}
						else
						{
							g.discard();
						}
					}

					parseCode = g.isDiscarded();
					break;
				}
				case MaybeEndOfBlock:
				{
					if(c == '@')
					{
						codeBlockType_ = OutsideCodeBlock;
						state_ = UnitializedText;
						location_ = stream_->location();
						return ';';
					}
					else
					{
						ParsingErrorException ex(
							stream_->position(),
							"Invalid terminator of the multi-line code block"
						);
						throw ex;
					}
					break;
				}
				case EndOfFile:
				{
					location_ = stream_->cursorLocation();
					return 0;
				}
			}
		}

		if(flushText && !textBuffer.empty())
		{
			value_ = new StrValue(textBuffer);
			textBuffer.clear();
			semicolonLocation_ = stream_->location();
			semiconPending_ = true;
			return yy::parser::token::LITERAL;
		}

		if(parseCode)
		{
			int token = codeParser_->parseNext();
			if(token)
			{
				value_ = codeParser_->value();
				location_ = codeParser_->location();
				hasCode_ = true;
				return token;
			}
		}
	}
}
